El presente documento presenta un hipotético caso real de proyecto de minería de datos. En el proyecto, se tratará de aplicar diversas técnicas y algoritmos de tratamiento de datos, con el objetivo de obtener reglas y conocimiento que pueda servir para el negocio del cliente.
Todo parte de una breve presentación del cliente…
Hola, me llamo Juan Schlemmer. Soy CEO a la cadena de tiendas presenciales Gourmet. Nos dedicamos al negocio de la alimentación y bebida de calidad. Tenemos presencia en tiendas en Europa y Estados Unidos. El negocio no nos va mal, pero me gustaría poder aumentar la facturación de la empresa. No tengo claro si aumentando las ventas o reduciendo gastos … El otro día jugando al golf con un compañero que se dedica al negocio de las comunicaciones en comentó que había puesto en marcha un proyecto de … Big Data o Minería de Datos o no sé qué del conocimiento … no lo tengo claro … Me dijo que le aconsejaron acciones concretas a partir de datos recogidos de su negocio y que está contento … La cuestión es que os enviarán unos datos extraídos de nuestro ERP para que me hagáis esto de la minería. Por favor si tenéis dudas con los datos preguntar a nuestros técnicos. ¿Cuándo podré tener un primer informe? ¿Dentro de un mes? Perfecto …
A partir de esto, se analizan los datos proporcionados y se trata de dar respuesta a las inquietudes del cliente que, en un primer momento, son expresadas de manera algo vaga. Se propondrán diversas soluciones de negocio a partir del conocimiento adquirido.
Mediante modelos de tres tipos distintos, se pretende extraer conocimiento que repercuta en un mayor beneficio para el negocio. Se usarán los siguientes tipos de modelos:
library(tidyverse)
library(lubridate)
library(kableExtra)Comenzamos importando los datos de los que dispone el cliente. Para nombrar las columnas de los conjuntos de datos, se utilizan los nombres presentes en el fichero .db2 proporcionado por el cliente.
#### IMPORTACION de datos -----------------
#cliente
cliente <- read.csv("cliente.csv", encoding = 'UTF-8', header = FALSE)
names(cliente) <- c("cod.cliente", "nombre.cliente", "sexo", "fecha.nacimiento",
"estado.civil", "direccion", "profesion", "numero.hijos", "region",
"nacionalidad", "total.compras", "puntos.acumulados")
# tienda --- rel con pais
tienda <- read.csv("tienda.csv", encoding = 'UTF-8', header = FALSE)
names(tienda) <- c("nombre", "direccion", "superficie", "formato.tienda", "pais", "tipo.zona")
# pais
pais <- read.csv("pais.csv", encoding = 'UTF-8', header = FALSE)
names(pais) <- c("nombre.pais", "extension", "poblacion" , "nombre.region")
# proveedor
proveedor <- read.csv("proveedor.csv", encoding = 'UTF-8', header = FALSE)
names(proveedor) <- c("cod.proveedor", "nombre.proveedor", "persona.contacto" ,
"direccion","telefono","periodo.pago",
"pago.pendiente", "alcance")
# producto ----- relacionada con proveedor
producto <- read.csv("producto.csv", encoding = 'UTF-8', header = FALSE)
names(producto) <- c("cod.producto", "descripcion", "nombre.pais" ,
"coste","precio.venta","tipo.unidad",
"nombre.subfamilia", "marca", "cod.proveedor" )
# seccion
seccion <- read.csv("seccion.csv", encoding = 'UTF-8', header = FALSE)
names(seccion) <- c("nombre.seccion", "descripcion")
# familia -- rel seccion
familia <- read.csv("familia.csv", encoding = 'UTF-8', header = FALSE)
names(familia) <- c("nombre.familia", "descripcion", "nombre.seccion")
# subfamilia - rel con familia
subfamilia <- read.csv("subfamilia.csv", encoding = 'UTF-8', header = FALSE)
names(subfamilia) <- c("nombre.subfamilia", "descripcion", "nombre.familia")
# cabecera.ticket ---------- rel con cliente
cabecera.ticket <- read.csv("cabeceraticket.csv", encoding = 'UTF-8', header = FALSE)
names(cabecera.ticket) <- c("cod.venta", "nombre.tienda", "fecha" ,
"hora","forma.pago","cod.cliente", "importe.total",
"total.unidades", "puntos.ticket")
# lineas.ticket ---------- rel con venta, producto, tienda, cabecera, promocion
lineas.ticket <- read.csv("lineasticket.csv", encoding = 'UTF-8', header = FALSE)
names(lineas.ticket) <- c("cod.linea", "cod.venta", "nombre.tienda" ,
"cod.producto","cantidad","precio.venta",
"nombre.promocion", "cod.cabecera")
# promocion ---------- rel con seccion, producto, tienda, pais, familia, region
promocion <- read.csv("promocion.csv", encoding = 'UTF-8', header = FALSE)
names(promocion) <- c("nombre.promocion", "tipo.promocion", "coste" ,
"fecha.inicio","fecha.fin","cod.producto",
"nombre.familia", "nombre.seccion", "nombre.tienda",
"nombre.region", "nombre.pais")
# pedido ---------- rel con producto, tienda
pedido <- read.csv("pedido.csv", encoding = 'UTF-8', header = FALSE)
names(pedido) <- c("cod.pedido", "nombre.tienda", "cod.producto" ,
"precio.compra","cantidad.solicitada","fecha.solicitud",
"cantidad.entregada", "fecha.entrega")cliente contiene datos de los clientes que han comprado en la cadena. Hay datos tanto de clientes particulares como de empresas. Guarda datos de interacción con la tienda, como número de compras o puntos acumulados y otros datos relativos al cliente: nacionalidad, número de hijos, estado civil, profesión o dirección.
producto almacena datos sobre los diversos productos a la venta en la cadena: país de origen, coste, precio de venta, marca o código del proveedor que lo suministra, entre otros.
pais hace referencia a los países de procedencia de los productos que se venden en las tiendas.
pedido contiene datos sobre los pedidos que se han realizado en las diferentes tiendas, con detalles como la fecha de solicitud y entrega o sobre los productos entregados.
tienda guarda información sobre las diversas tiendas con las que cuenta la cadena.
seccion incluye las cinco secciones en las que se clasifican los productos vendidos en la cadena: Vinos, Espumosos, Licores, Quesos o Postres.
familia se refiere a las diferentes clasificaciones posibles dentro de cada sección de productos.
subfamilia referencia las diversas clasificaciones dentro de cada familia de productos.
proveedor contiene información sobre los diversos comercios de los cuales la cadena compra los productos.
promocion incluye datos sobre las promociones llevadas a cabo en las distintas tiendas. No obstante, apenas cuenta con cinco valores.
cabecera.ticket guarda los valores de los tickets de compra que se crean con las transacciones con los clientes de la tienda. Incluye información como la tienda, la fecha y hora, la forma de pago, el total de unidades o el importe total de la compra.
lineas.ticket se refiere a las distintas lineas que componen los tickets de compra, es decir, a los distintos productos que se incluyen en cada una de las compras.
A continuación, se muestran las 12 tablas de partida.
clienteproductopaispedidotiendaseccionfamiliasubfamiliaproveedorpromocioncabecera.ticketlineas.ticketTransformamos los valores de las columnas de fechas de formato character a formato date, mediante el paquete lubridate.
# Limpieza pedido --------------------
pedido$fecha.solicitud <- ymd(pedido$fecha.solicitud)
pedido$fecha.entrega <- ymd(pedido$fecha.entrega)
# Limpieza cliente --------------------
cliente$fecha.nacimiento <- ymd(cliente$fecha.nacimiento)
# Limpieza cabecera.ticket --------------------
cabecera.ticket$fecha <- ymd(cabecera.ticket$fecha)
# Limpieza promocion --------------------
promocion$fecha.inicio <- ymd(promocion$fecha.inicio)
promocion$fecha.fin <- ymd(promocion$fecha.fin)Apreciamos el resultado efectuando summary en la tabla cliente:
summary(cliente)## cod.cliente nombre.cliente sexo fecha.nacimiento
## 0000001R: 1 Hut Pizzeria : 6 Empresa: 805 Min. :1910-02-04
## 0000002J: 1 Norma : 6 Hombre :2076 1st Qu.:1939-02-25
## 0000003B: 1 Payne Henry : 6 Mujer :1188 Median :1951-11-09
## 0000004N: 1 Brigham Ernest: 5 Mean :1952-02-17
## 0000005E: 1 Noma : 5 3rd Qu.:1967-08-11
## 0000006C: 1 Tanner : 5 Max. :1979-12-31
## (Other) :4063 (Other) :4036
## estado.civil direccion
## : 805 116 Sussex Gardens ,London EC1 : 7
## Casado/a :1318 15 Bury St, St James'S ,London NW1 : 7
## Divorciado/a : 651 Corso Buenos Aires 3 , Milano : 7
## Soltero/a :1219 Piazzale Suppercortemaggiore 4 , Milano : 7
## Viudo/a : 76 Via Ludovico Ariosto 22 , Milano : 7
## Viale Lombardia 55 , Milano : 7
## (Other) :4027
## profesion numero.hijos
## Economistas,Abogados & Admin.Empresas :707 Min. :0.0000
## Gerentes & Directivos :703 1st Qu.:0.0000
## Doctores & Profesionales de la Salud :663 Median :0.0000
## Ingenieros & Especialistas :507 Mean :0.9684
## Architectos,Decoradores & Humanistas :485 3rd Qu.:2.0000
## Catering :339 Max. :4.0000
## (Other) :665 NA's :805
## region nacionalidad total.compras
## Norte Europa:1821 España :1349 Min. : 0.000
## Norteamérica: 899 Estados Unidos: 899 1st Qu.: 5.000
## Sur Europa :1349 Reino Unido :1821 Median : 8.000
## Mean : 8.729
## 3rd Qu.:12.000
## Max. :48.000
##
## puntos.acumulados
## Min. : 5.00
## 1st Qu.: 7.00
## Median : 9.00
## Mean : 11.54
## 3rd Qu.: 12.00
## Max. :105.00
##
Nos proponemos saber que productos ha comprado cada cliente, para poder llevar a cabo un análisis más preciso del comportamiento de los clientes en la tienda. Para llevar a cabo dicha unión, debemos preparar los datos un poco más.
Detectamos que hay discrepancias entre levels de las variables cuantitativas o factor cod.producto y cod. cliente en las distintas tablas. Debemos hacer que los levels sean iguales, es decir, eliminar espacios en blanco. De no hacerlo, como consecuencia, al efectuar los join, obtendríamos muchos valores NA.
### imprimimos levels
# hay
#
lvls <- sort(unique(c(levels(cliente$cod.cliente),
levels(cabecera.ticket$cod.cliente))))
lvls.prod <- sort(unique(c(levels(producto$cod.producto),
levels(lineas.ticket$cod.producto))))
### Detectamos espacios en blanco en los lvls de cliente cabecera.ticket$cod.cliente,
# los eliminamos
levels(cabecera.ticket$cod.cliente)<- str_trim(levels(cabecera.ticket$cod.cliente))
# idem para lineas.ticket$cod.producto
levels(lineas.ticket$cod.producto)<- str_trim(levels(lineas.ticket$cod.producto))# transformamos en string los codigos cliente y producto
cliente$cod.cliente <- as.character(cliente$cod.cliente)
cabecera.ticket$cod.cliente <- as.character(cabecera.ticket$cod.cliente)
producto$cod.producto <- as.character(producto$cod.producto)
lineas.ticket$cod.producto <- as.character(lineas.ticket$cod.producto)
# nombre.subfamilia
producto$nombre.subfamilia <- as.character(producto$nombre.subfamilia)
subfamilia$nombre.subfamilia <- as.character(subfamilia$nombre.subfamilia)Procedemos con la unión de la tabla de cliente con la de producto. Para ello, deberemos realizar un recorrido desde cliente, pasando por cabecera ticket y lineas ticket, como se muestra en el siguiente bloque de código.
cl_prod <- cliente %>% left_join(cabecera.ticket, by = "cod.cliente") %>%
inner_join(lineas.ticket, by = "cod.venta") %>%
left_join(producto, by = "cod.producto")
# agregamos la variable beneficio
cl_prod <- cl_prod %>% mutate(beneficio = precio.venta.y - coste)
cl_prodSe ha agregado una variable nueva a la tabla cliente producto. Esta variable corresponde al beneficio que la tienda obtiene como resultado de la interacción con el cliente, en el proceso de venta de cada uno de los productos que este ha comprado. Esta variable será utilizada en los análisis y algoritmos posteriores.
A continuación, se realiza una exploración gráfica de los datos, que nos permitirá tener un mayor conocimiento de las distintas variables que manejamos.
Se incluye un análisis de las transacciones que han tenido lugar en la cadena.
cl_prod %>%
mutate(hora = as.factor(hora)) %>%
group_by(hora) %>%
summarise(clientes = n_distinct(cod.cliente)) %>%
ggplot(aes(x=hora, y=clientes)) +
geom_bar(stat="identity", fill="steelblue3", show.legend=FALSE) +
geom_label(aes(label=clientes)) +
labs(title="Clientes por hora") + scale_x_discrete(labels = as.character(9:22), breaks = 9:22 )Se observa como las horas con mayor número de clientes son de las 20 a las 22.
En la siguiente gráfica, el 1 representa el domingo y el 7 el sábado.
cl_prod %>%
mutate(dia.semana = as.factor(wday(fecha))) %>%
group_by(dia.semana) %>%
summarise(clientes = n_distinct(cod.cliente)) %>%
ggplot(aes(x=dia.semana, y=clientes)) +
geom_bar(stat="identity", fill="steelblue3", show.legend=FALSE) +
geom_label(aes(label=clientes)) +
labs(title="Clientes por día de semana") Vemos que los días de la semana con más clientes son los viernes y sábados.
# forma de pago
cl_prod %>%
group_by(forma.pago) %>%
summarise(count = length(forma.pago)) %>%
ggplot(aes(x=forma.pago, y=count)) +
geom_bar(stat="identity", fill="steelblue3", show.legend=FALSE) +
geom_label(aes(label=count)) +
labs(title="Forma de pago") + theme(axis.text.x = element_text(angle = 45, hjust = 1))Los medios de pago más empleados son las tarjetas (crédito, débito y otras) con cerca de 50k pagos. Le siguen los cheques, con casi 30k y el pago en efectivo, con cerca de 20k.
Se incluye un análisis de los clientes que compran en la cadena.
# genero de cliente
cl_prod %>%
group_by(sexo) %>%
summarise(count = length(sexo)) %>%
filter(sexo != "Empresa") %>%
ggplot(aes(x=sexo, y=count)) +
geom_bar(stat="identity", fill="sienna2", show.legend=FALSE) +
geom_label(aes(label=count)) +
labs(title="Genero") Se aprecia que la mayoría de los clientes del establecimiento son hombres.
# profesión del cliente
cl_prod %>%
group_by(profesion) %>%
summarise(count = length(profesion)) %>%
ggplot(aes(x=profesion, y=count)) +
geom_bar(stat="identity", fill="sienna2", show.legend=FALSE) +
labs(title="Profesión de los clientes") + theme(axis.text.x = element_text(angle = 45, hjust = 1))Entre los grupos con mayor número de clientes, observamos un gran número de clientes que se dedican al sector de la alimentación y también otro grupo dedicado al mundo empresarial (economistas y gerentes).
# nacionalidad cliente
cl_prod %>%
group_by(nacionalidad) %>%
summarise(count = length(nacionalidad)) %>%
ggplot(aes(x=nacionalidad, y=count)) +
geom_bar(stat="identity", fill="sienna2", show.legend=FALSE) +
geom_label(aes(label=count)) +
labs(title="Nacionalidad de los clientes") + theme(axis.text.x = element_text(angle = 45, hjust = 1))Los clientes son únicamente de 3 nacionalidades distintas, destacando el Reino Unido, con más de 40k clientes.
# numero de clientes y empresas
cl_prod %>%
mutate(empresa_dummy = ifelse(sexo == 'Empresa' , 'Empresa', 'Cliente Particular')) %>%
group_by(empresa_dummy) %>%
summarise(count = n_distinct(cod.cliente)) %>%
ggplot(aes(x=empresa_dummy, y=count)) +
geom_bar(stat="identity", fill="sienna2", show.legend=FALSE) +
geom_label(aes(label=count)) +
labs(title="Número de clientes particulares y empresas")La cadena cuenta con 3142 clientes particulares y 781 empresas.
Se incluye un análisis de los productos que vende la cadena.
# nombre.pais procedencia producto
cl_prod %>%
group_by(nombre.pais) %>%
summarise(count = length(nombre.pais)) %>%
ggplot(aes(x=nombre.pais, y=count)) +
geom_bar(stat="identity", fill="skyblue1", show.legend=FALSE) +
labs(title="País de procedencia de los productos") + theme(axis.text.x = element_text(angle = 45, hjust = 1))Observamos como una gran cantidad de productos provienen de España y Francia.
# productos-- nombre.subfamilia
cl_prod %>% left_join(subfamilia, by = "nombre.subfamilia") %>%
left_join(familia, by = "nombre.familia") %>%
group_by(nombre.seccion) %>%
summarise(count = length(nombre.seccion)) %>%
filter (count > 3000) %>%
ggplot(aes(x=nombre.seccion, y=count)) +
geom_bar(stat="identity", fill="skyblue3", show.legend=FALSE) +
labs(title="Secciones de los productos") + theme(axis.text.x = element_text(angle = 45, hjust = 1))Los productos que más vende la tienda son vinos y quesos. Esto podría explicar la gran cantidad de productos españoles y franceses (países con grandes vinos y quesos).
Se muestran valores de beneficio por hora y por día de la semana.
cl_prod %>%
mutate(hora = as.factor(hora)) %>%
group_by(hora) %>%
summarise(beneficio = sum(beneficio)) %>%
ggplot(aes(x=hora, y=beneficio)) +
geom_bar(stat="identity", fill="palegreen1", show.legend=FALSE) +
labs(title="Beneficio por hora") + scale_x_discrete(labels = as.character(9:22), breaks = 9:22 ) El mayor beneficio, en el cómputo global, se obtiene sobre las 21 horas.
cl_prod %>%
mutate(dia.semana = as.factor(wday(fecha))) %>%
group_by(dia.semana) %>%
summarise(beneficio = sum(beneficio)) %>%
ggplot(aes(x=dia.semana, y=beneficio)) +
geom_bar(stat="identity", fill="palegreen1", show.legend=FALSE) +
geom_label(aes(label=beneficio)) +
labs(title="Beneficio por día de semana") Se aprecia como los días dónde el beneficio es mayor son el viernes y el sábado.
En general, los valores del beneficio están altamente relacionados con los valores del número de transacciones. Esto es algo que cabe esperar, pues cuantas más transacciones se produzcan, más probable será que la empresa aumente los beneficios.
Nos preguntamos si hay alguna diferencia del beneficio obtenido por empresas o clientes.
# beneficio: cliente y empresa
cl_prod %>%
mutate(empresa_dummy = ifelse(sexo == 'Empresa' , 'Empresa', 'Cliente Particular')) %>%
group_by(empresa_dummy) %>%
summarise(beneficio = sum(beneficio)) %>%
ggplot(aes(x=empresa_dummy, y=beneficio)) +
geom_bar(stat="identity", fill="palegreen1", show.legend=FALSE) +
geom_label(aes(label=beneficio)) +
labs(title="Beneficio total por tipo de cliente")El beneficio total es superior para clientes. No obstante, sabemos que hay muchos más clientes particulares que empresas. Por tanto, para obtener una medida que compare de manera más justa ambos grupos, mostramos el beneficio medio obtenido con cada uno.
# beneficio: cliente y empresa
cl_prod %>%
mutate(empresa_dummy = ifelse(sexo == 'Empresa' , 'Empresa', 'Cliente Particular')) %>%
group_by(empresa_dummy) %>%
summarise(beneficio = mean(beneficio)) %>%
ggplot(aes(x=empresa_dummy, y=beneficio)) +
geom_bar(stat="identity", fill="palegreen1", show.legend=FALSE) +
geom_label(aes(label=round(beneficio, digits = 2))) +
labs(title="Beneficio medio por tipo de cliente")Por cada empresa se obtiene un beneficio en media ligeramente superior al obtenido para clientes particulares.
Tras el análisis inicial de los datos, procedemos a la construcción de los modelos anteriormente mencionados.